#ifndef Vector3dH
#define Vector3dH

/*
|| Mimo że wszystkie funcjie da się napisać w jednej linijce,
|| to jednak rozpisanie są dla speeda. Przydalo by sie SSE ;-D
|| Jest jeszcze cała masa innych ciekawych funcji,
|| które się przydaja podczas zabaw z geometrią ^_^
|| autor: Kamil Szcześniak lionbestpl@gmail.com
*/

#define EPSILON 0.00001
#define Number float
#define NumberType float

#include <algorithm>
#include "vector2d.hpp"

//template< typename NumberType >
class Vector3d
{
	public:

		friend std::ostream& operator << (std::ostream& s, const Vector3d &m);

		Vector3d(NumberType _x,NumberType _y,NumberType _z):x(_x),y(_y),z(_z) {}; // wektor z współżędnymi x,y
		//Vector3d(NumberType t):x(cos(t)),y(sin(t)),z(0) {}; // wektor normalny dla kąta
		Vector3d(const NumberType vec[3]):x(vec[0]), y(vec[1]), z(vec[2]) {} // wektor z tablicy
		Vector3d(){};
		Vector3d operator+(Vector3d l) const           // dodawanie wektorów
		{
			return Vector3d(x+l.x,y+l.y,z+l.z);
		}
		Vector3d operator-(Vector3d l) const           // odejmowanie wektorów
		{
			return Vector3d(x-l.x,y-l.y,z-l.z);
		}
		Vector3d operator-() const                     // wektor przeciwny
		{
			return Vector3d(-x,-y,-z);
		}
		Vector3d operator*(NumberType l) const         // mnożenie wektora przez skalar
		{
			return Vector3d(x*l,y*l,z*l);
		}
		Vector3d& operator*=(NumberType l)              // mnożenie wektora przez skalar w miejscu
		{
			x*=l;
			y*=l;
			z*=l;
			return *this;
		}
		Vector3d operator/(NumberType l) const  // dzielenie wektora przez skalar
		{
			return Vector3d(x/l,y/l,z/l);
		}

		Vector3d& operator/=(NumberType l) // dzielenie wektora przez skalar w miejscu
		{
			x/=l;
			y/=l;
			z/=l;
			return *this;
		}

		Vector3d crossProdukt(const Vector3d& vec) const   // mnożenie wektorowe
		{
			return Vector3d(
				y * vec.z - z * vec.y,
				z * vec.x - x * vec.z,
				x * vec.y - y * vec.x
				);
		}

		Vector3d operator*(const Vector3d& vec) const   // mnożenie wektorowe
		{
			return Vector3d(
				y * vec.z - z * vec.y,
				z * vec.x - x * vec.z,
				x * vec.y - y * vec.x
				);
		}

		Vector3d& operator*=(const Vector3d& vec)  // mnożenie wektorowe w miejscu
		{
			return ((*this)=(*this) * vec);
		}
		Vector3d& operator += (const Vector3d& vec) // dodawanie wektorów w miejscu
		{
		    x+=vec.x;
		    y+=vec.y;
		    z+=vec.z;
		    return *this;
		}
		Vector3d& operator -= (const Vector3d& vec) // odemowanie wektorów w miejscu
		{
		   x-=vec.x;
		   y-=vec.y;
		   z-=vec.z;
		   return *this;
		}
		NumberType length() const  // długość
		{
			return sqrt(length2());
		}
		NumberType length2() const   // długość kwadrat
		{
			return x*x + y*y + z*z;
		}
 		Vector3d& normalize() // długość 1
 		{
			NumberType temp = length2();
			if(temp==1) return *this;
			temp = sqrt(temp);
			x/=temp;
			y/=temp;
			z/=temp;
			return *this;
 		};
		Vector3d normalized() const // zwraca wektor o długości 1
		{
			NumberType temp = length2();
			if(temp==1) return Vector3d(*this);
			temp = sqrt(temp);
			return Vector3d(x/temp,y/temp,z/temp);
		}
		NumberType dotProduct(const Vector3d& vec) const// mnozenie scalarne
		{
			return (x*vec.x + y*vec.y + z*vec.z);

		}
		Vector3d projection(const Vector3d& normal) const //rzutowanie na wektor
		{
			NumberType temp = (x*normal.x + y*normal.y + z*normal.z) / normal.length2();
			return Vector3d(temp * normal.x,temp * normal.y,temp * normal.z);
		}
		Vector3d& projectioned(const Vector3d& normal) //rzutowanie na wektor
		{
			NumberType temp = (x*normal.x + y*normal.y + z*normal.z) / normal.length2();
			x = temp * normal.x;
			y = temp * normal.y;
			z = temp * normal.z;
			return *this;
		}
		bool operator==(const Vector3d& vec) const  // no coż czasami są błędy float...
		{
			NumberType dx = std::abs(x - vec.x);
			NumberType dy = std::abs(y - vec.y);
			NumberType dz = std::abs(z - vec.z);
			return (dx < EPSILON) && (dy < EPSILON) && (dz < EPSILON);
		}
		bool operator!=(const Vector3d& vec) const
		{
			return (x != vec.x) || (y != vec.y) || (z != vec.z);
		}
		bool operator>(const Vector3d& vec) const
		{
			return length2() > vec.length2();
		}
		bool operator<(const Vector3d& vec) const
		{
			return length2() < vec.length2();
		}
		bool operator>=(const Vector3d& vec) const
		{
			return length2() >= vec.length2();
		}
		bool operator<=(const Vector3d& vec) const
		{
			return length2() <= vec.length2();
		}
		// rzutowanie punktu na płaszczyznę normalx,normaly; kąt miedzy wektorami musi być 90st
		Vector2d plane(const Vector3d& normalX,const Vector3d& normalY) const
		{
			return Vector2d(
				this->dotProduct(normalX),
				this->dotProduct(normalY)
				);
		}

		Vector2d to2d(NumberType d) const
		{
			return Vector2d((x*d)/(z+d),(y*d)/(z+d));
		}

		Vector2d to2d() const
		{
			return Vector2d(x,y);
		}
		// kąt między płaszczyznami
		NumberType angle(const Vector3d& to) const
		{
			NumberType d = std::sqrt(length2()*to.length2());
			return atan2(
				(this->crossProdukt(to)).length() / d,
				dotProduct(to) / d
				);
		}

		Vector3d& flipX()
		{
			x=-x;
			return *this;
		}
		Vector3d flipedX() const
		{
			return Vector3d(-x,y,z);
		}
		Vector3d& flipY()
		{
			y=-y;
			return *this;
		}
		Vector3d flipedY() const
		{
			return Vector3d(x,-y,z);
		}
		Vector3d& flipZ()
		{
			z=-z;
			return *this;
		}
		Vector3d flipedZ() const
		{
			return Vector3d(x,y,-z);
		}
		Vector3d& flip(const Vector3d& norm)
		{
			NumberType temp = 2 * (x*norm.x + y*norm.y + z*norm.z) / norm.length2();
			x = (temp * norm.x) - x;
			y = (temp * norm.y) - y;
			z = (temp * norm.z) - z;
			return *this;
 		}
		Vector3d fliped(const Vector3d& norm) const
		{
			NumberType temp = 2 * (x*norm.x + y*norm.y + z*norm.z) / norm.length2();
			return Vector3d((temp * norm.x) - x,(temp * norm.y) - y,(temp * norm.z) - z);
		}
		Vector3d leftHand(Vector3d head)
		{
			//todo
			return Vector3d();
		}
		Vector3d rightHand(Vector3d head)
		{
			//todo
			return Vector3d();
		}
		union{
			struct{
				NumberType x;  // współżędna x
				NumberType y;  // współżędna x
				NumberType z;  // współżędna x
			};
			NumberType v[3]; // czasami przydaje sie dostęp tablicowy
		};
		static inline Vector3d zero(){
			return Vector3d(0,0,0);
		};
};

std::ostream& operator << (std::ostream& s, const Vector3d &m) {
    return s << "{ " << m.x << ", " << m.y << ", " << m.z << "}";
    }

#endif
